package de.tud.cdc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.util.encoders.Base64;

public class CalendarDecryption {
	
	/**
	 * Name of the secret PGP key-ring.
	 */
	 private String SECRET_KEYRING;
	 private static final String KEY_NOT_FOUND = "sk for message not found.";
	 private static final String ERROR = "---ERROR---";
	 /**
		 * {@link Logger} to print on stdout.
		 */
	private final Logger logger;
	private String identities;
	
	/**
	 * Buffer for a very long mail that arrives in multiple parts from the javascript part of this extension.
	 */
	private StringBuffer mailBuffer;
	/**
	 * to find out the beginning of encrypted code.
	 */
	int index;
	
	public CalendarDecryption() {
		Security.addProvider(new BouncyCastleProvider());
		mailBuffer = new StringBuffer();
		logger = Logger.getLogger("CBLog");
		logger.setLevel(Level.INFO);
	}	
	/**
	 * Core function for pgp-encryption using bouncy castle.
	 * 
	 * @param in
	 *            the pgp encrypted mail as {@link InputStream}
	 * @param passwd
	 *            password for the private pgp key in the keyring as char array
	 * @param charset
	 *            the charset of this mail. If unknown this could be null.
	 * @return the decrypted mail as {@link String}
	 * @throws IOException
	 * @throws NoSuchProviderException
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	private String decryptCalendarContent(InputStream in, String strpasswd, String charset, String pgp_path, String gpg_secret_keyring) throws IOException, NoSuchProviderException {
		this.setSecretKeyRing(pgp_path, gpg_secret_keyring);
		char [] passwd = StringToChar(strpasswd);
		in = PGPUtil.getDecoderStream(in);
		StringBuffer error = new StringBuffer();
		try {
			FileInputStream keyInStream = null;
			try {
				keyInStream  = new FileInputStream(SECRET_KEYRING);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				return e.toString();
			}
			
			PGPEncryptedDataList enc;
			PGPObjectFactory pgpF = new PGPObjectFactory(in);
			Object o = pgpF.nextObject();
			//
			// the first object might be a PGP marker packet.
			//
			if (o instanceof PGPEncryptedDataList) {
				enc = (PGPEncryptedDataList) o;
			} else {
				enc = (PGPEncryptedDataList) pgpF.nextObject();
			}
			
			//
			// find the secret key
			//
			Iterator it = enc.getEncryptedDataObjects();
			PGPPrivateKey sKey = null;
			PGPPublicKeyEncryptedData pbe = null;
			PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyInStream));
			keyInStream.close();
			while (sKey == null && it.hasNext()) {
				pbe = (PGPPublicKeyEncryptedData) it.next();
				sKey = findSecretKey(pgpSec, pbe.getKeyID(), passwd);
			}
			if (sKey == null) {
				return KEY_NOT_FOUND;
			}
			
			InputStream clear = pbe.getDataStream(sKey, "BC");
			PGPObjectFactory plainFact = new PGPObjectFactory(clear);
			Object message = plainFact.nextObject();

			PGPObjectFactory pgpFact = null;
			if (message instanceof PGPCompressedData) {
				PGPCompressedData cData = (PGPCompressedData) message;
				 pgpFact = new PGPObjectFactory(cData.getDataStream());

				message = pgpFact.nextObject();
			}
			ByteArrayOutputStream bOut = new ByteArrayOutputStream();
			if (message instanceof PGPLiteralData) {
				PGPLiteralData ld = (PGPLiteralData) message;

				InputStream unc = ld.getInputStream();
				int ch;
				try {
					while ((ch = unc.read()) >= 0) {
						bOut.write(ch);
					}
				} catch (Exception e) {
					
					e.printStackTrace();
				}
			
			} else if (message instanceof PGPOnePassSignatureList) {
			

				// write message out
				PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();

				InputStream unc = ld.getInputStream();
				int ch;

				while ((ch = unc.read()) >= 0) {
					bOut.write(ch);
				}
				unc.close();

			} else {
				// write message out
				PGPLiteralData ld = (PGPLiteralData) message;

				InputStream unc;
				try{
					unc = ld.getInputStream();
				} catch (Exception e) {
					// why is here a Nullpointer thrown?
					logger.finest("Exception while getting inputstream from BC (decryptMail).\n"+e.getMessage());
					NullPointerException ex = new NullPointerException();
					ex.setStackTrace(e.getStackTrace());
					throw ex;
				}
				int ch;

				while ((ch = unc.read()) >= 0) {
					bOut.write(ch);
				}
				unc.close();
			}

			if (pbe.isIntegrityProtected()) {
				if (!pbe.verify()) {
					logger.warning("message failed integrity check");
				} else {
					logger.finest("message integrity check passed");
				}
			} else {
				logger.finest("no message integrity check");
			}
			
			/**
			 * Try to get correct encoding here.
			 * Does not work every time!
			 */
			if (charset == null || charset.equals("")){
				logger.warning("No Charset for this mail. So use ISO-8859-1. May lead to incorrect encoding.");
				charset = "ISO-8859-1";
//				charset = "UTF-8";
			}
			byte[] ba = bOut.toByteArray();
			String decrypted = null;
			try{
				decrypted = new String(ba, charset);
				if (!decrypted.toLowerCase().contains("charset") && decrypted.matches("<U+[0-9A-Fa-f]{4}>")){
					
				}
				decrypted = decrypted.replaceAll("=([A-Fa-f0-9]{2})", "%$1");
			} catch (Exception e) {
				logger.severe("Wrong encoding: "+charset + " " +e.getLocalizedMessage());
				String resultString = new String(ba);
				decrypted = resultString;
			}
			return decrypted; // TODO: Encoding: need to check if UTF-8... 
		} catch (PGPException e) {
			if (e.toString().equals("org.bouncycastle.openpgp.PGPException: checksum mismatch at 0 of 20"))
				return KEY_NOT_FOUND;
			logger.severe("Error in decrypt mail:\n"+e);
			error.append(e.toString());
			if (e.getUnderlyingException() != null) {
				e.getUnderlyingException().printStackTrace();
				error.append(e.getUnderlyingException().toString());
			}
		} finally {
			in.close();
		}
		return ERROR;
	}
	
	public String decryptCalendarContent(String input, String strpasswd, String charset, String pgp_path, String gpg_secret_keyring) throws NoSuchProviderException, IOException{
	  if(input != " "){	
		this.index = input.indexOf(".org/");
		InputStream in = new ByteArrayInputStream(Base64.decode(input.substring(index+5).getBytes()));
		return decryptCalendarContent(in, strpasswd, charset, pgp_path, gpg_secret_keyring);
	  }else{
		  return "";
	  }
	  
	}
	
			
		//Find Key and decrypt Mailsubject:
		public String decryptCalendarTitel(String encHdr, String strpasswd, String charset, String pgp_path, String gpg_secret_keyring){
			this.setSecretKeyRing(pgp_path, gpg_secret_keyring);
			
			if (!encHdr.startsWith("-----SUBJECT ENCRYPTED-----") || !!encHdr.startsWith("-----SUBJECT ENCRYPTED-------NO KEY--") || encHdr.length()<28) return "";
			try {
			    InputStream in = new ByteArrayInputStream(Base64.decode(encHdr.substring(27).getBytes()));
				return decryptCalendarContent(in, strpasswd, charset, pgp_path, gpg_secret_keyring);
			} catch (NoSuchProviderException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return "";
		}
		
		/**
		 * Search a secret key ring collection for a secret key corresponding to
		 * keyID if it exists.
		 * 
		 * @param pgpSec
		 *            a secret key ring collection.
		 * @param keyID
		 *            keyID we want.
		 * @param pass
		 *            passphrase to decrypt secret key with.
		 * @return the private key as {@link PGPPrivateKey} if found, else NULL
		 * @throws PGPException
		 * @throws NoSuchProviderException
		 */
		private PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) throws PGPException, NoSuchProviderException {
			PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);

			if (pgpSecKey == null) {
				return null;
			}

			PGPPrivateKey pKey = null;
			try {
				pKey = pgpSecKey.extractPrivateKey(pass, "BC");
			} catch (PGPException e) {
				if (e.toString().equals("org.bouncycastle.openpgp.PGPException: checksum mismatch at 0 of 20")){
					this.identities = getIDs(pgpSec, keyID);
				}
			}
			return pKey;
		}
		
		/**
		 * Get the identities of a keyID from a Secret key ring collection
		 * 
		 * @param pgpSec
		 *            the {@link PGPSecretKeyRingCollection}
		 * @param keyID
		 *            the ID to search for as long
		 * @return an String with all found identities, seperated in lines
		 */
		@SuppressWarnings("unchecked")
		private String getIDs(PGPSecretKeyRingCollection pgpSec, long keyID){
			PGPSecretKeyRing pgpSecKeyRing;
			StringBuffer sb = new StringBuffer();
			try {
				pgpSecKeyRing = pgpSec.getSecretKeyRing(keyID);
				Iterator<String> id = pgpSecKeyRing.getPublicKey().getUserIDs();
				sb.append("<HTML><BODY>");
				while (id.hasNext()){
					sb.append(id.next().replaceAll("<", "").replaceAll(">", ""));
					sb.append("<BR/>");
				}
				sb.append("</HTML></BODY>");
			} catch (PGPException e) {
				logger.warning("Error while trying to get the id's of the key. "+  e.getLocalizedMessage());
				throw new NullPointerException("Error while trying to get the id's of the key.");
			}
			return sb.toString();
		}
		
		private char [] StringToChar(String str){
			char [] myCharArray = str.toCharArray ();
			return myCharArray;
		}
		
	  private String setSecretKeyRing(String pgp_path, String gpg_secret_keyring){
			this.SECRET_KEYRING = pgp_path + File.separator + gpg_secret_keyring;
			File secring = new File(this.SECRET_KEYRING);
			String result = "";
			if (!secring.isFile()){
				result = "Can't find the GPG secret keyring. Please edit the path and restart if you use GPG encrypted emails.";
				this.SECRET_KEYRING = null;
			}
			return result;
		}
	

}
